package com.tuananh.radnom.model.protocol.snmp;

import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.HashMap;

import org.eclipse.persistence.oxm.annotations.XmlDiscriminatorValue;
import org.snmp4j.mp.SnmpConstants;

import com.tuananh.radnom.model.NetInterface;
import com.tuananh.radnom.model.discovery.DiscoverNetInt;
import com.tuananh.radnom.model.iputils.IPRange;
import com.tuananh.radnom.model.protocol.service.ActiveDiscover;
import com.tuananh.radnom.model.protocol.snmp.SnmpService.MyBroadcastIPListener;
import com.tuananh.radnom.model.utils.StringUtils;

@XmlDiscriminatorValue("ActiveSnmp")
public class ActiveSnmp extends ActiveDiscover {

	// public ArrayList<String> visitedDevices = new ArrayList<String>();
	SnmpInfo getSnmpInfo(String address) {
		SnmpInfo snmpInfo = new SnmpInfo();
		// get the infomation neccessary to query SNMP service for address
		HashMap<String, String> params = new HashMap<String, String>();
		for (IPRange range : this.getRange()) {
			HashMap<String, String> temp = range.getParamByIp(address);
			if (temp != null) {
				params.putAll(temp);
			}
		}
		String versionStr = params.get("version") != null ? params
				.get("version") : "1";
		String retriesStr = params.get("retries") != null ? params
				.get("retries") : "1";
		String timeoutStr = params.get("timeout") != null ? params
				.get("timeout") : "2000";
		int retries = Integer.parseInt(retriesStr);
		long timeout = Long.parseLong(timeoutStr);
		int version = Integer.parseInt(versionStr);
		ArrayList<String> securesInfos = new ArrayList<String>();
		switch (version) {
		case SnmpConstants.version1:
		case SnmpConstants.version2c:
			String readCommunity = params.get("read") != null ? params
					.get("read") : "public";
			String writeCommunity = params.get("write") != null ? params
					.get("write") : readCommunity;
			securesInfos.add(readCommunity);
			securesInfos.add(writeCommunity);
			break;
		case SnmpConstants.version3:
			break;
		}
		snmpInfo.setAddress(address);
		snmpInfo.setRetries(retries);
		snmpInfo.setTimeout(timeout);
		snmpInfo.setSecuresInfo(securesInfos);
		snmpInfo.setVersion(version);
		return snmpInfo;
	}

	private ArrayList<String> nextHopDiscovery(
			ArrayList<String> visitedDevices, ArrayList<String> result,
			String... inputAddresses) throws IOException {
		if (visitedDevices == null)
			visitedDevices = new ArrayList<String>();
		if (result == null)
			result = new ArrayList<String>();
		for (String address : inputAddresses) {
			// add the address of the device to the visited device set
			visitedDevices.add(address);
			// get the infomation neccessary to query SNMP service for address
			SnmpInfo snmpInfo = getSnmpInfo(address);
			if (!result.contains(address))
				result.add(address);
			// begin discover
			if (SnmpService.check(
					snmpInfo.version,
					"udp:" + address + "/161",
					snmpInfo.retries,
					snmpInfo.timeout,
					snmpInfo.getSecuresInfo().toArray(
							new String[snmpInfo.getSecuresInfo().size()])) != 0) {
				// Query 2 entry object ipRouteNextHop and ipRouteType from the
				// snmp agent
				// ipRouteNextHop = ".1.3.6.1.2.1.4.21.1.7.0" , ipRouteType =
				// ".1.3.6.1.2.1.4.21.1.8.0"
				ArrayList<String> ipRouteNextHop = SnmpService.snmpWalk(
						".1.3.6.1.2.1.4.21.1.7",
						snmpInfo.version,
						"udp:" + address + "/161",
						snmpInfo.retries,
						snmpInfo.timeout,
						snmpInfo.getSecuresInfo().toArray(
								new String[snmpInfo.getSecuresInfo().size()]));
				ArrayList<String> ipRouteType = SnmpService.snmpWalk(
						".1.3.6.1.2.1.4.21.1.8",
						snmpInfo.version,
						"udp:" + address + "/161",
						snmpInfo.retries,
						snmpInfo.timeout,
						snmpInfo.getSecuresInfo().toArray(
								new String[snmpInfo.getSecuresInfo().size()]));
				// return if nothing found
				if (ipRouteNextHop == null)
					continue;
				for (int i = 0; i < ipRouteNextHop.size(); i++) {
					String nextHop = SnmpService
							.getValueFromBinding(ipRouteNextHop.get(i));
					// Online accept the route type 4 = "indirect"
					if (SnmpService.getValueFromBinding(ipRouteType.get(i))
							.equals("4")) {
						if (!result.contains(nextHop))
							result.add(nextHop);
						if (!visitedDevices.contains(nextHop)) {
							nextHopDiscovery(visitedDevices, result, nextHop);
						}
					}
				}
			}
		}
		return result;
	}

	private ArrayList<String> arpCacheDiscovery(
			ArrayList<String> visitedDevices, ArrayList<String> result,
			String... addresses) throws IOException {
		if (visitedDevices == null)
			visitedDevices = new ArrayList<String>();
		if (result == null)
			result = new ArrayList<String>();
		for (String address : addresses) {
			// add the address of the device to the visited device set
			visitedDevices.add(address);
			// get the infomation neccessary to query SNMP service for address
			SnmpInfo snmpInfo = getSnmpInfo(address);
			if (!result.contains(address))
				result.add(address);
			// begin discover
			if (SnmpService.check(
					snmpInfo.version,
					"udp:" + address + "/161",
					snmpInfo.retries,
					snmpInfo.timeout,
					snmpInfo.getSecuresInfo().toArray(
							new String[snmpInfo.getSecuresInfo().size()])) != 0) {
				// Query 2 entry object ipNetToMediaAddress and ipNetToMediaType
				// from the snmp agent
				// ipNetToMediaAddress = "1.3.6.1.2.1.4.22.1.3.0",
				// ipNetToMediaType="1.3.6.1.2.1.4.22.1.4.0"
				ArrayList<String> ipNetToMediaAddress = SnmpService.snmpWalk(
						".1.3.6.1.2.1.4.22.1.3",
						snmpInfo.version,
						"udp:" + address + "/161",
						snmpInfo.retries,
						snmpInfo.timeout,
						snmpInfo.getSecuresInfo().toArray(
								new String[snmpInfo.getSecuresInfo().size()]));
				ArrayList<String> ipNetToMediaType = SnmpService.snmpWalk(
						".1.3.6.1.2.1.4.22.1.4",
						snmpInfo.version,
						"udp:" + address + "/161",
						snmpInfo.retries,
						snmpInfo.timeout,
						snmpInfo.getSecuresInfo().toArray(
								new String[snmpInfo.getSecuresInfo().size()]));
				// return if nothing found
				if (ipNetToMediaAddress == null)
					continue;

				for (int i = 0; i < ipNetToMediaAddress.size(); i++) {
					String nextAdress = SnmpService
							.getValueFromBinding(ipNetToMediaAddress.get(i));

					if (SnmpService
							.getValueFromBinding(ipNetToMediaType.get(i))
							.equals("3"))
						if (!visitedDevices.contains(nextAdress)) {
							if (!result.contains(nextAdress))
								result.add(nextAdress);
							arpCacheDiscovery(visitedDevices, result,
									nextAdress);
						}
				}
			}
			;
		}
		return result;
	}

	public boolean isListContain(ArrayList<String> list, String value) {
		for (String str : list)
			if (str.equals(value))
				return true;
		return false;
	}

	@Override
	public ArrayList<String> broadcastDiscover() {
		ArrayList<String> broadcastIPs = StringUtils.split(
				this.getBroadcastConfig(), ",");

		ArrayList<String> result = new ArrayList<String>();
		// TODO Auto-generated method stub
		for (String address : broadcastIPs) {
			SnmpInfo snmpInfo = getSnmpInfo(address);
			MyBroadcastIPListener listener = new MyBroadcastIPListener();
			ArrayList<String> oids = new ArrayList<String>();
			oids.add(".1.3.6.1.2.1.1.1.0");
			ArrayList<String> temp = SnmpService
					.broadcastGetOIDs(listener, oids, address,
							snmpInfo.version, snmpInfo.retries,
							snmpInfo.timeout,
							(String[]) snmpInfo.securesInfo.toArray());
			for (String str : temp) {
				if (!isListContain(result, str))
					result.add(str);
			}
		}
		return result;
	}

	@Override
	public ArrayList<String> discoverNetInt() {
		ArrayList<String> inputIPs = StringUtils.split(this.getInputIPs(), ",");
		// TODO Auto-generated method stub
		ArrayList<String> results = new ArrayList<String>(), nextHopDiscover, arpDiscover;
		ArrayList<String> visitedDevices = new ArrayList<String>();
		try {
			nextHopDiscover = nextHopDiscovery(visitedDevices,
					new ArrayList<String>(),
					inputIPs.toArray(new String[inputIPs.size()]));
			results.addAll(nextHopDiscover);
			this.setChanged();
			// clear the visited device set
			visitedDevices = new ArrayList<String>();

			arpDiscover = arpCacheDiscovery(visitedDevices,
					new ArrayList<String>(),
					nextHopDiscover.toArray(new String[nextHopDiscover.size()]));
			arpDiscover.removeAll(nextHopDiscover);// remove duplicate addresses
			results.addAll(arpDiscover);

		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		for (String str : results) {
			DiscoverNetInt discoveredNetInt = new DiscoverNetInt();
			discoveredNetInt.setIp(str);
			updateObservers(discoveredNetInt);
		}
		return results;
	}

	@Override
	public HashMap<String, String> discoverType(ArrayList<String> discoveredIPs) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public HashMap<String, String> discoverConnection(ArrayList<String> inputIPs) {
		// TODO Auto-generated method stub
		return null;
	}

	public void run() {
		// TODO Auto-generated method stub
		System.out.println("ActiveSnmp started");
		discoverNetInt();
	}
}
